package cn.blnp.net.flowable.task.service.error;

import cn.hutool.core.map.MapUtil;
import lombok.extern.slf4j.Slf4j;
import org.flowable.engine.HistoryService;
import org.flowable.engine.RepositoryService;
import org.flowable.engine.RuntimeService;
import org.flowable.engine.TaskService;
import org.flowable.engine.repository.Deployment;
import org.flowable.engine.repository.ProcessDefinition;
import org.flowable.engine.runtime.ProcessInstance;
import org.flowable.task.api.Task;
import org.flowable.task.api.TaskQuery;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;

import javax.annotation.Resource;
import java.util.HashMap;
import java.util.Map;

/**
 * <h3>服务任务的异常处理</h3>
 *
 * @author <a href="mailto:blnp.yibin@qq.com">lyb</a>
 * @version 1.0
 * @since 2025/5/13 11:25
 */
@Slf4j
@SpringBootTest
public class ServiceTaskErrorTest {

    @Resource
    private RepositoryService repositoryService;
    @Resource
    private RuntimeService runtimeService;
    @Resource
    private TaskService taskService;
    @Resource
    private HistoryService historyService;

    /**
     *  <p>
     *      使用服务任务，当执行自定义逻辑时，经常需要捕获对应的业务异常，并在流程中进行处理。对于该问题，Flowable提供了多种解决方式。
     *  </p>
     *  <h3>抛出BPMN错误</h3>
     *  <p>
     *      Flowable支持在以下3种服务任务的自定义逻辑中抛出BPMN错误。
     *      <ol>
     *          <li>通过flowable:class属性指定的Java类的execute()方法。</li>
     *          <li>通过flowable:delegateExpression属性指定的委托类的execute()方法。</li>
     *          <li>通过flowable:expression属性指定的表达式方法。</li>
     *      </ol>
     *      在以上3种情况下，抛出类型为BpmnError的特殊FlowableExeption会被工作流引擎捕获，并被转发到对应的错误处理器（如错误边界事件或错误事件子流程）。
     *  示例代码如下：{@link cn.blnp.net.flowable.task.service.error.ThrowBpmnErrorDelegate}
     *  </p>
     **/
    @Test
    public void testThrow() {

    }

    /**
     *  <h2>配置异常映射</h2>
     *  <p>
     *   &emsp;&emsp;Flowable还支持配置异常映射，直接将Java异常映射为业务错误。下面看一个示例，示例代码如下：
     *  <pre>
     *      {@code
     *          <serviceTask id="serviceTask1" name="服务任务" flowable:class="com.bpm.example.servicetask.custom.MyJavaDelegate">
     *              <extensionElements>
     *                  <flowable:mapException errorCode="customErrorCode1">
     *                      com.bpm.example.servicetask.custom.CustomException
     *                  </flowable:mapException>
     *              </extensionElements>
     *          </serviceTask>
     *      }
     *  </pre>
     *  <u>
     *   &emsp;&emsp;以上服务任务定义中，通过flowable:mapException元素配置了异常映射，表示如果服务任务抛出com.bpm.example.servicetask.custom.CustomException异常，
     *  工作流引擎会将其捕获并转换为指定errorCode的BPMN错误，然后像普通BPMN错误一样进行处理。而其他未配置映射的异常，仍将被抛出到API调用处。
     *  </u>
     *  也可以结合使用includeChildExceptions属性，映射特定异常的所有子异常，示例代码如下：
     *  <pre>
     *      {@code
     *          <serviceTask id="serviceTask2" name="服务任务" flowable:class="com.bpm.example.servicetask.custom.MyJavaDelegate" >
     *              <extensionElements>
     *                  <flowable:mapException errorCode="customErrorCode1" includeChildExceptions="true">
     *                      com.bpm.example.servicetask.custom.CustomException
     *                  </flowable:mapException>
     *              </extensionElements>
     *          </serviceTask>
     *      }
     *  </pre>
     *  &emsp;&emsp;配置了includeChildExceptions属性，Flowable会将CustomException的任何直接或间接的子类转换为指定errorCode的BPMN错误。当不配置includeChildExceptions属性时，
     *  工作流引擎将视其值为false。Flowable还支持配置默认映射。默认映射是一个不指定类的映射，可以匹配任何Java异常，示例代码如下：
     *  <pre>
     *      {@code
     *          <serviceTask id="serviceTask1" name="服务任务" flowable:class="com.bpm.example.servicetask.custom.MyJavaDelegate">
     *              <extensionElements>
     *                  <flowable:mapException errorCode="defaultErrorCode"/>
     *              </extensionElements>
     *          </serviceTask>
     *      }
     *  </pre>
     *  &emsp;&emsp;当配置了默认映射后，一旦服务任务抛出异常，工作流引擎就会按照从上到下的顺序检查默认映射之外的其他所有映射，并使用第一个匹配的映射，只有在所有映射都不能成功匹配时才使用默认映射。
     *  需要注意的是，只有第一个不指定类的映射会作为默认映射，默认映射会忽略includeChildExceptions属性。
     *  </p>
     **/
    @Test
    public void testMapping() {

    }

    /**
     *  <h2>指定异常顺序流</h2>
     *  <p>
     *      在发生异常时，如果服务任务是通过flowable:class属性指定的Java类，或者通过flowable:delegateExpression属性指定的委托类，实现了ActivityBehavior接口，可以在其execute()方法中
     *  控制流程离开当前服务任务沿指定的外出顺序流继续流转。当库存不足时会抛出异常。
     *      <b>流程定义文件：</b>{@link flow/serviceTaskThrowsExceptionBehavior.bpmn20.xml}<br/>
     *      <b>流程预览图：</b><br/>
     *      <img src="https://cdn4.winhlb.com/2025/05/13/6822dd5ca7553.png" />
     *  </p>
     **/
    @Test
    public void testErrorOrder() {
        Deployment deploy = repositoryService.createDeployment()
                .name("指定顺序流转案例")
                .addClasspathResource("flow/serviceTaskThrowsExceptionBehavior.bpmn20.xml")
                .deploy();
        log.info("部署流程定义：{}", deploy.getId());
        ProcessDefinition procDef = repositoryService.createProcessDefinitionQuery()
                .deploymentId(deploy.getId())
                .singleResult();
        log.info("流程定义编号：{}", procDef.getId());
        //启动流程实例
        ProcessInstance procInst = runtimeService.startProcessInstanceById(procDef.getId());
        TaskQuery taskQuery = taskService.createTaskQuery()
                .processInstanceId(procInst.getId());

        //查询第一个任务
        Task firstTask = taskQuery.singleResult();
        log.info("第一个用户任务为：" + firstTask.getName());

        //初始化流程变量
        Map<String, Object> varMap = MapUtil.builder(new HashMap<String, Object>())
                .put("applyNum", 10)
                .put("description", "申请领用10台电脑")
                .build();

        //办理第一个任务
        taskService.complete(firstTask.getId(), varMap);
        //查询第二个任务
        Task secondTask = taskQuery.singleResult();
        log.info("第二个用户任务为：" + secondTask.getName());

        /**
         *  演示结果：
         *  2025-05-13 13:52:17.171  INFO 64736 --- [           main] c.b.n.f.t.s.error.ServiceTaskErrorTest   : 部署流程定义：6cd9a5b4-2fbe-11f0-aae4-50ebf6ec8f6d
         * 2025-05-13 13:52:17.176  INFO 64736 --- [           main] c.b.n.f.t.s.error.ServiceTaskErrorTest   : 流程定义编号：6d3ab177-2fbe-11f0-aae4-50ebf6ec8f6d
         * 2025-05-13 13:52:17.253  INFO 64736 --- [           main] c.b.n.f.t.s.error.ServiceTaskErrorTest   : 第一个用户任务为：领用申请
         * 2025-05-13 13:52:17.303  INFO 64736 --- [           main] .t.s.e.InventoryCheckingActivityBehavior : 申请领用数量：10
         * 2025-05-13 13:52:17.303  INFO 64736 --- [           main] .t.s.e.InventoryCheckingActivityBehavior : 申请原因：申请领用10台电脑
         * 2025-05-13 13:52:17.303 ERROR 64736 --- [           main] .t.s.e.InventoryCheckingActivityBehavior : 库存数量为：5，库存不足！
         * 2025-05-13 13:52:17.358  INFO 64736 --- [           main] c.b.n.f.t.s.error.ServiceTaskErrorTest   : 第二个用户任务为：缺货上报
         **/
    }
}
